﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Text;
using System.Text.RegularExpressions;
using System.Web.Routing;
using MvcDependency.Internal;
using System.IO;
using System.Web.Mvc;

namespace MvcDependency
{
    public class DependencyManager
    {
        private readonly HttpContextBase _httpContext;
        internal const string ContextKey = "MvcDependencyManager";
        private const string JavaScriptPlaceHolder = "<!--[Javascript:Name=\"{0}\", Version=\"{1}\", Path=\"{2}\"]//-->";
        internal static Regex JsMarkupRegex = new Regex("<!--\\[Javascript:Name=\"(?<renderer>.*?)\", Version=\"(?<version>.*?)\", Path=\"(?<path>.*?)\"\\]//-->", RegexOptions.Compiled);
        internal static Regex CssMarkupRegex = new Regex("<!--\\[Css:Name=\"(?<renderer>.*?)\", Version=\"(?<version>.*?)\"\\]//-->", RegexOptions.Compiled);
        private const string JavascriptScriptFormat = "<script src=\"{0}{1}?cdv={2}\" type=\"text/javascript\"></script>{3}";
        private const string CssFormat = "<link href=\"{0}{1}?cdv={2}\" rel=\"stylesheet\" type=\"text/css\" />{3}";
        static readonly FuncEqualityComparer<DependencyItem> Comparer = new FuncEqualityComparer<DependencyItem>(x => x.Path);
        private readonly HashSet<DependencyItem> _dependencies;
        private static readonly object SyncLock = new object();
        private readonly object _instanceLock = new object();

        public DependencyManager(HttpContextBase httpContext)
        {
            _httpContext = httpContext;
            if(_httpContext.Items.Contains(ContextKey)) throw new InvalidOperationException("Only one DependencyManager may exist in a context");
            _httpContext.Items[ContextKey] = this;
            _dependencies = new HashSet<DependencyItem>(Comparer);
        }

        internal static DependencyManager Get(HttpContextBase httpContext)
        {
            if(httpContext.Items.Contains(ContextKey) == false)
            {
                lock(SyncLock)
                {
                    if(httpContext.Items.Contains(ContextKey) == false)
                    {
                        var loader = new DependencyManager(httpContext);
                        return loader;
                    }
                }
            }
            return httpContext.Items[ContextKey] as DependencyManager;
        }

        internal void Add(string filePath, string group, ushort order, DependencyType type)
        {
            lock(_instanceLock)
            {
                var versionOnly = filePath.StartsWith("http", StringComparison.InvariantCultureIgnoreCase);
                if(_dependencies.Add(new DependencyItem{ DependencyType = type, 
                    Group = group, 
                    Order = order, 
                    Path = filePath,
                    VersionOnly = versionOnly}) == false)
                {
                    throw new DuplicateResourceException(filePath);
                }
            }
        }

        public string Render(DependencyType type, string group, string path, string version)
        {
            if(type == DependencyType.Javascript)
            {
                return string.Format(JavaScriptPlaceHolder, group, version, path);
            }
            throw new NotImplementedException();
        }

        public string ReplaceJsPlaceHolder(string html)
        {
            return JsMarkupRegex.Replace(html,
                    x =>
                        {
                            var grp = x.Groups["renderer"];
                            var pathOverride = x.Groups["path"];
                            var version = x.Groups["version"];
                            if(pathOverride != null && !string.IsNullOrEmpty(pathOverride.ToString()))
                            {
                                var helper = new UrlHelper(new RequestContext(_httpContext, RouteTable.Routes.GetRouteData(_httpContext)));
                                return string.Format(JavascriptScriptFormat, string.Empty, helper.Content("~/" + pathOverride), version.ToString(), Environment.NewLine);
                            }
                            if(grp != null)
                            {
                                return RenderInternal(DependencyType.Javascript, version.ToString(), grp.ToString());
                            }
                            return x.ToString();
                        });
        }

        public string ReplaceCssPlaceHolder(string html)
        {
            return CssMarkupRegex.Replace(html,
                    x =>
                    {
                        var grp = x.Groups["renderer"];
                        var version = x.Groups["version"];
                        var js = RenderInternal(DependencyType.CSS, version.ToString(), grp.ToString());
                        return js;
                    });
        }

        internal string RenderInternal(DependencyType type, string version, string group)
        {
            var items = _dependencies.Where(x => x.DependencyType == type)
                .Where(x => x.Group == group)
                .OrderBy(x => x.Order)
                .ToArray();

            return RenderInternal(type, version, items);
        }

        private string RenderInternal(DependencyType type, string version, IEnumerable<DependencyItem> items)
        {
            var format = type == DependencyType.Javascript
                             ? JavascriptScriptFormat
                             : CssFormat;

            var prePath = GetPrePath(string.Empty);
            var resources = new StringBuilder();
            foreach(var item in items)
            {
                resources.AppendFormat(format, 
                    item.VersionOnly ? string.Empty : prePath, 
                    item.Path, version, 
                    Environment.NewLine);
            }

            return resources.ToString();
        }

        internal string RenderVersionOnlyInternal(DependencyType type, string group, string version)
        {
            var items = _dependencies.Where(x => x.DependencyType == type)
                .Where(x => x.VersionOnly)
                .Where(x => x.Group == group)
                .OrderBy(x => x.Order)
                .ToArray();

            return RenderInternal(type, version, items);
        }

        private string GetPrePath(string path)
        {
            var prePath = string.Empty;
            if(_httpContext.IsDebuggingEnabled == false)
                prePath = Path.Combine(prePath, path);
            if(prePath == "/")
                prePath = string.Empty;
            return prePath;
        }

        internal class DependencyItem
        {
            public string Group { get; set; }
            public DependencyType DependencyType { get; set; }
            public string Path { get; set; }
            public ushort Order { get; set; }
            public bool VersionOnly { get; set; }
        }
    }
}